Developper guide ================ Want to contribute ? add your own parametric object ? Archipack's api provide all needed parts to handle parametric objects, as easy to import subclasses, so you may focus on your base object mesh building. Here you'll find a "quick start" dev guide with all required parts. `Download sample file `_ Imports ------- Implementation sample """"""""""""""""""""" .. code-block:: python :caption: Minimal required imports :name: Imports-py import bpy from bpy.types import Operator, PropertyGroup, Mesh, Panel # Minimal required property types from bpy.props import ( FloatProperty, BoolProperty, CollectionProperty ) from mathutils import Vector # Bmesh made easy from .bmesh_utils import BmeshEdit as bmed # Manipulable from .archipack_manipulator import Manipulable # Preset system from .archipack_preset import ArchipackPreset, PresetMenuOperator # Base for Propertygroup and create tool Operator from .archipack_object import ArchipackCreateTool, ArchipackObject PropertyGroup ------------- The main propertyGroup is the heart of parametric object, generating mesh on parameter changes using bmesh, and updating "manipulators" location according. .. note:: Separate properties for vertex, faces, material indexes and uvmaps is not required, made like this as for such simple object it allow to keep code clean. More complex objects may require to build uvs maps knowing vertex location, and such design pattern would not work. Naming convention """"""""""""""""" archipack\_ prefix, then your meanfull class name Requirements """""""""""" * **MUST** inherit from ArchipackObject and Manipulable * **MUST** implement update() function generating / refreshing mesh and manipulators location, see :ref:`updating-manipulator` * **MUST** implement setup_manipulators() function filling manipulators Collection see :ref:`available-manipulators` * **MUST** have auto_update property to disable mesh updating for bulk updates * Properties **SHOULD** call update (use update=update in property definition) Implementation sample (a box) """"""""""""""""""""""""""""" .. code-block:: python :caption: Minimal implementation using all features :name: toolkit-property-py :emphasize-lines: 5,11,17,23,25,105 def update(self, context): self.update(context) class archipack_myobject(ArchipackObject, Manipulable, PropertyGroup): """ Archipack toolkit sample""" x = FloatProperty( name="Width", default=2.0, min=0.01, unit='LENGTH', subtype='DISTANCE', update=update ) y = FloatProperty( name="Depth", default=2.0, min=0.01, unit='LENGTH', subtype='DISTANCE', update=update ) z = FloatProperty( name="Height", default=2.0, min=0.01, unit='LENGTH', subtype='DISTANCE', update=update ) auto_update = BoolProperty( # Wont save auto_update state in any case options={'SKIP_SAVE'}, default=True, update=update ) @property def verts(self): """ Object vertices coords """ x = 0.5 * self.x y = 0.5 * self.y z = self.z return [ (-x, y, 0), (-x, -y, 0), (x, -y, 0), (x, y, 0), (-x, y, z), (-x, -y, z), (x, -y, z), (x, y, z) ] @property def faces(self): """ Object faces vertices index """ return [ (0, 1, 2, 3), (7, 6, 5, 4), (7, 4, 0, 3), (4, 5, 1, 0), (5, 6, 2, 1), (6, 7, 3, 2) ] @property def uvs(self): """ Object faces uv coords """ return [ [(0, 0), (0, 1), (1, 1), (1, 0)], [(0, 0), (0, 1), (1, 1), (1, 0)], [(0, 0), (0, 1), (1, 1), (1, 0)], [(0, 0), (0, 1), (1, 1), (1, 0)], [(0, 0), (0, 1), (1, 1), (1, 0)], [(0, 0), (0, 1), (1, 1), (1, 0)] ] @property def matids(self): """ Object material indexes for each face """ return [0, 0, 0, 0, 0, 0] def setup_manipulators(self): if len(self.manipulators) < 1: # add manipulator for x property s = self.manipulators.add() s.prop1_name = "x" s.type_key = 'SIZE' # add manipulator for y property s = self.manipulators.add() s.prop1_name = "y" s.type_key = 'SIZE' # add manipulator for z property s = self.manipulators.add() s.prop1_name = "z" s.type_key = 'SIZE' # draw this one on xz plane s.normal = Vector((0, 1, 0)) def update(self, context): # provide support for "copy to selected" o = self.find_in_selection(context, self.auto_update) if o is None: return # dynamically create manipulators when needed self.setup_manipulators() # update your mesh from parameters bmed.buildmesh(context, o, self.verts, self.faces, matids=self.matids, uvs=self.uvs, weld=False) # update manipulators location (3d location in object coordsystem) x, y = 0.5 * self.x, 0.5 * self.y self.manipulators[0].set_pts([(-x, -y, 0), (x, -y, 0), (1, 0, 0)]) self.manipulators[1].set_pts([(-x, -y, 0), (-x, y, 0), (-1, 0, 0)]) self.manipulators[2].set_pts([(x, -y, 0), (x, -y, self.z), (-1, 0, 0)]) # always restore context self.restore_context(context) Panel (T panel in UI) --------------------- Main ui panel to modify object properties. Naming convention """"""""""""""""" ARCHIPACK\_PT\_ prefix then your object name Requirements """""""""""" * **MUST** poll using your PropertyGroup class * **MUST** call manipulate operator * **MUST** call presets operators Implementation sample (a box) """"""""""""""""""""""""""""" .. code-block:: python :caption: Minimal implementation using all features :name: toolkit-panel-py :emphasize-lines: 11,23,29-36 class ARCHIPACK_PT_myobject(Panel): bl_idname = "ARCHIPACK_PT_myobject" bl_label = "MyObject" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'ArchiPack' @classmethod def poll(cls, context): # ensure your object panel only show when active object is the right one return archipack_myobject.filter(context.active_object) def draw(self, context): o = context.active_object if not archipack_myobject.filter(o): return layout = self.layout # retrieve datablock of your object props = archipack_myobject.datablock(o) # Manipulate mode operator layout.operator('archipack.myobject_manipulate', icon='HAND') box = layout.box() row = box.row(align=True) # Presets operators row.operator("archipack.myobject_preset_menu", text=bpy.types.ARCHIPACK_OT_myobject_preset_menu.bl_label) row.operator("archipack.myobject_preset", text="", icon='ZOOMIN') row.operator("archipack.myobject_preset", text="", icon='ZOOMOUT').remove_active = True row = layout.row() box = row.box() box.label(text="Size") box.prop(props, 'x') box.prop(props, 'y') box.prop(props, 'z') Create Tool ----------- This operator implement creation tool for your object. You may add it in N Panel and/or shift+A menu to allow users to create your objects. Naming convention """"""""""""""""" ARCHIPACK\_OT\_ prefix and then your object name bl\_idname name is archipack. prefix and then your object name Requirements """""""""""" * **MUST** inherit from ArchipackCreateTool * bl_idname **MUST** match your object's class name * **MUST** call load_preset, passing your class property datablock * **MUST** create an object, a mesh and link to scene * **MUST** add your PropertyGoup Implementation sample """"""""""""""""""""" .. code-block:: python :caption: Minimal implementation using all features :name: toolkit-create-py :emphasize-lines: 1-2,11,14,17,20,27 class ARCHIPACK_OT_myobject(ArchipackCreateTool, Operator): bl_idname = "archipack.myobject" bl_label = "Myobject" bl_description = "Create Myobject" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} def create(self, context): # Create an empty mesh datablock m = bpy.data.meshes.new("Myobject") # Create an object using the mesh datablock o = bpy.data.objects.new("Myobject", m) # Add your properties on mesh datablock d = m.archipack_myobject.add() # Link object into scene context.scene.objects.link(o) # select and make active o.select = True context.scene.objects.active = o # Load preset into datablock self.load_preset(d) # add a material self.add_material(o) return o def execute(self, context): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = bpy.context.scene.cursor_location o.select = True context.scene.objects.active = o # Start manipulate mode self.manipulate() return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") return {'CANCELLED'} Preset menu operator -------------------- Preset system require two operators, this one display graphical preset menu. Naming convention """"""""""""""""" ARCHIPACK\_OT\_ prefix and then your object name then \_preset\_menu bl\_idname name is archipack. prefix and then your object name then \_preset\_menu Requirements """""""""""" * **MUST** define preset_subdir using PropertyGroup class name Implementation sample """"""""""""""""""""" .. code-block:: python :caption: Minimal implementation using all features :name: toolkit-preset-menu-py :emphasize-lines: 4 class ARCHIPACK_OT_myobject_preset_menu(PresetMenuOperator, Operator): bl_idname = "archipack.myobject_preset_menu" bl_label = "Myobject preset" preset_subdir = "archipack_myobject" Preset add/destroy operator --------------------------- Preset system require two operators, this one allow user to create and destroy presets. Naming convention """"""""""""""""" Use ARCHIPACK\_OT\_ prefix and then your object name then \_preset bl\_idname name is archipack. prefix and then your object name then \_preset Requirements """""""""""" * **MUST** define preset_menu using the class name of preset menu operator * **MAY** define blacklist, an array of property names you don't want to save in presets Implementation sample """"""""""""""""""""" .. code-block:: python :caption: Minimal implementation using all features :name: toolkit-preset-py :emphasize-lines: 5,9 class ARCHIPACK_OT_myobject_preset(ArchipackPreset, Operator): """Add a Myobject Preset""" bl_idname = "archipack.myobject_preset" bl_label = "Add Myobject preset" preset_menu = "ARCHIPACK_OT_myobject_preset_menu" @property def blacklist(self): return ['manipulators'] Manipuate Operator ------------------ This operator allow user to enter/exit from "manipulate" mode. Naming convention """"""""""""""""" Use ARCHIPACK\_OT\_ prefix and then your object name then \_manipulate bl\_idname name is archipack. prefix and then your object name then \_manipulate Requirements """""""""""" * **MUST** poll using your propertyGroup class * **MUST** call .manipulable_invoke(context) Implementation sample """"""""""""""""""""" .. code-block:: python :caption: Minimal implementation using all features :name: toolkit-manipulate-py :emphasize-lines: 9,13 class ARCHIPACK_OT_myobject_manipulate(Operator): bl_idname = "archipack.myobject_manipulate" bl_label = "Manipulate" bl_description = "Manipulate" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(self, context): return archipack_myobject.filter(context.active_object) def invoke(self, context, event): d = archipack_myobject.datablock(context.active_object) d.manipulable_invoke(context) return {'FINISHED'} Register/Unregister ------------------- Each archipack object must register / unregister itself Requirements """""""""""" * PropertyGroup **MUST** register/unregister and be added as CollectionProperty of Mesh * Panel, create, preset and manipulate Operators **MUST** register/unregister Implementation sample """"""""""""""""""""" .. code-block:: python :caption: Minimal implementation using all features :name: toolkit-register-py :emphasize-lines: 1-3,11-13 def register(): bpy.utils.register_class(archipack_myobject) Mesh.archipack_myobject = CollectionProperty(type=archipack_myobject) bpy.utils.register_class(ARCHIPACK_PT_myobject) bpy.utils.register_class(ARCHIPACK_OT_myobject) bpy.utils.register_class(ARCHIPACK_OT_myobject_preset_menu) bpy.utils.register_class(ARCHIPACK_OT_myobject_preset) bpy.utils.register_class(ARCHIPACK_OT_myobject_manipulate) def unregister(): bpy.utils.unregister_class(archipack_myobject) del Mesh.archipack_myobject bpy.utils.unregister_class(ARCHIPACK_PT_myobject) bpy.utils.unregister_class(ARCHIPACK_OT_myobject) bpy.utils.unregister_class(ARCHIPACK_OT_myobject_preset_menu) bpy.utils.unregister_class(ARCHIPACK_OT_myobject_preset) bpy.utils.unregister_class(ARCHIPACK_OT_myobject_manipulate) Modify the \_\_init\_\_.py -------------------------- Imports """"""" .. code-block:: python imp.reload(archipack_myobject) .. code-block:: python from . import archipack_myobject Create panel """""""""""" class TOOLS_PT_Archipack_Create(Panel) in draw function: .. code-block:: python col = row.column() subrow = col.row(align=True) subrow.operator("archipack.myobject_preset_menu", text="myobj", icon_value=icons["door"].icon_id ).preset_operator = "archipack.myobject" subrow.operator("archipack.myobject_preset_menu", text="", icon='GREASEPENCIL' ).preset_operator = "archipack.myobject_draw" Shift + a and add mesh menu """"""""""""""""""""""""""" on draw_menu(): .. code-block:: python layout.operator("archipack.door_preset_menu", text="Door", icon_value=icons["door"].icon_id ).preset_operator = "archipack.door" Register and unregister function """""""""""""""""""""""""""""""" .. code-block:: python archipack_myobject.register() .. code-block:: python archipack_myobject.unregister()